package BLC

import (
	"../wallet"
	"bytes"
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rand"
	"crypto/sha256"
	"encoding/gob"
	"encoding/hex"
	"fmt"
	"log"
	"math/big"
)

const subsidy=10

type Transaction struct {
	//交易ID
	ID []byte
	//交易输入
	Vin []*TXInput
	//交易输出
	Vout []*TXOutput
}


//1.判断当前交易是否是CoinBase
func (tx *Transaction)IsCoinBase()bool{
	return len(tx.Vin)==1 && tx.Vin[0].Vout==-1 && len(tx.Vin[0].Txid)==0
}

//创建创世交易
func NewCoinBase(to,data string)*Transaction{
	if data==""{
		data=fmt.Sprintf("Reward the money to %s",to)
	}
	//创建特殊的交易输入
	vin:=&TXInput{[]byte{},-1,nil,[]byte{}}
	//交易输出
	vout:=NewTXOutput(10,to)
	//返回交易的列表
	tx:=Transaction{nil,[]*TXInput{vin},[]*TXOutput{vout}}
	tx.SetID()
	return &tx
}

//把整个交易进行sha256，得到hash值
func (tx *Transaction)SetID(){
	var buff bytes.Buffer
	var hash [32]byte

	encode:=gob.NewEncoder(&buff)
	err:=encode.Encode(tx)
	if err!=nil{
		panic(err)
	}

	hash=sha256.Sum256(buff.Bytes())
	tx.ID=hash[:]
}



//创建一个新的交易
func NewSimpleTransaction(from,to string,amount int,bc *BlockChain)*Transaction{

	wallets,_:=wallet.NewWallets()
	w:=wallets.WalletsMap[from]

	//输入
	var inputs []*TXInput
	//输出
	var outputs []*TXOutput

	//找到足够的未花费的输出
	acc,validOutput:=bc.FindUnUTXOs(from,amount)

	//判断是否够付钱
	if acc<amount{
		panic(from+"余额不足!")
	}

	//建立输入
	for txid,outs:=range validOutput{
		txID,err:=hex.DecodeString(txid)
		if err!=nil{
			panic(err)
		}

		for _,out:=range outs{
			input:=&TXInput{txID,out,nil,w.PublicKey}
			inputs=append(inputs,input)
		}
	}

	//建立输出，转账
	output:=NewTXOutput(amount,to)
	outputs=append(outputs,output)

	//找零
	output=NewTXOutput(acc-amount,from)
	outputs=append(outputs,output)

	tx:=Transaction{nil,inputs,outputs}
	tx.SetID()

	//数字签名
	bc.Signature(&tx,w.PrivateKey)

	return &tx
}

func (tx *Transaction)TrimmedCopy()Transaction{
	var inputs []*TXInput
	var outputs []*TXOutput

	for _,vin:=range tx.Vin{
		inputs=append(inputs,&TXInput{vin.Txid,vin.Vout,nil,nil})
	}

	for _,vout:=range tx.Vout{
		outputs=append(outputs,&TXOutput{vout.Value,vout.Ripemd160Hash})
	}

	return Transaction{tx.ID,inputs,outputs}

}

func (tx *Transaction)Serilize()[]byte{
	var buff bytes.Buffer

	encoder:=gob.NewEncoder(&buff)
	err:=encoder.Encode(tx)
	if err!=nil{
		panic(err)
	}
	return buff.Bytes()

}

func (txCopy *Transaction)Hash()[]byte{
	tx:=*txCopy
	tx.ID=[]byte{}
	hash:=sha256.Sum256(tx.Serilize())
	return hash[:]
}

func (tx *Transaction)Sign(privateKey ecdsa.PrivateKey,prevTXs map[string]Transaction){

	if tx.IsCoinBase()==true{
		return
	}

	//对找到的交易进行一次筛选
	for _,vin:=range tx.Vin{
		if prevTXs[hex.EncodeToString(vin.Txid)].ID==nil{
			log.Panic("居然有的交易没有来源!")
		}
	}

	txCopy:=tx.TrimmedCopy()


	for inID,vin:=range tx.Vin{
		prevTX:=prevTXs[hex.EncodeToString(vin.Txid)]
		txCopy.Vin[inID].Signature=nil
		txCopy.Vin[inID].PubKey=prevTX.Vout[vin.Vout].Ripemd160Hash
		txCopy.ID=txCopy.Hash()
		txCopy.Vin[inID].PubKey=nil

		//签名代码
		r,s,err:=ecdsa.Sign(rand.Reader,&privateKey,txCopy.ID)
		if err!=nil{
			panic(err)
		}

		signature:=append(r.Bytes(),s.Bytes()...)

		tx.Vin[inID].Signature=signature
	}

}

func (tx *Transaction)VerifySignature(prevTXs map[string]Transaction)bool{
	if tx.IsCoinBase()==true{
		return true
	}

	//对找到的交易进行一次筛选
	for _,vin:=range tx.Vin{
		if prevTXs[hex.EncodeToString(vin.Txid)].ID==nil{
			log.Panic("居然有的交易没有来源!")
		}
	}

	txCopy:=tx.TrimmedCopy()

	for inID,vin:=range tx.Vin{
		prevTX:=prevTXs[hex.EncodeToString(vin.Txid)]
		txCopy.Vin[inID].Signature=nil
		txCopy.Vin[inID].PubKey=prevTX.Vout[vin.Vout].Ripemd160Hash
		txCopy.ID=txCopy.Hash()
		txCopy.Vin[inID].PubKey=nil

		//私钥 ID
		r:=big.Int{}
		s:=big.Int{}
		sigLen:=len(vin.Signature)
		fmt.Println("这个交易的签名的长度是:",sigLen)
		r.SetBytes(vin.Signature[:(sigLen/2)])
		s.SetBytes(vin.Signature[(sigLen/2):])

		curve:=elliptic.P521()

		//公钥 验证签名
		x:=big.Int{}
		y:=big.Int{}
		PubKeyLen:=len(vin.PubKey)
		x.SetBytes(vin.PubKey[:(PubKeyLen/2)])
		y.SetBytes(vin.PubKey[(PubKeyLen/2):])
		PublicKey:=ecdsa.PublicKey{curve,&x,&y}

		if ecdsa.Verify(&PublicKey,txCopy.ID,&r,&s)==false{
			return false
		}
	}

	return true

}


